package org.example.java.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class NIOClient {
  /* 标识数字*/
  private static int flag = 0;
  /*缓冲区大小*/
  private static int BLOCK = 4096;
  /*接受数据缓冲区*/
  private static ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
  /*发送数据缓冲区*/
  private static ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
  /*服务器端地址*/
  private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress("localhost", 18888);
  //选择器
  Selector selector;

  public NIOClient() throws IOException {
    // 打开socket通道
    SocketChannel channel = SocketChannel.open();
    // 设置为非阻塞方式
    channel.configureBlocking(false);
    // 打开选择器
    selector = Selector.open();
    // 注册连接服务端socket动作
    channel.register(selector, SelectionKey.OP_CONNECT);
    // 连接
    channel.connect(SERVER_ADDRESS);
  }

  private void doConnect(SelectionKey key) throws IOException {
    SocketChannel client;
    System.out.println("client connect");
    client = (SocketChannel) key.channel();
    // 判断此通道上是否正在进行连接操作。
    // 完成套接字通道的连接过程。
    if (client.isConnectionPending()) {
      client.finishConnect();
      System.out.println("完成连接!");
      sendBuffer.clear();
      sendBuffer.put("Hello Server".getBytes());
      sendBuffer.flip();
      client.write(sendBuffer);
    }
    client.register(selector, SelectionKey.OP_READ);
  }

  private void doWrite(SelectionKey key) throws IOException {
    SocketChannel client;
    String sendText;
    sendBuffer.clear();
    client = (SocketChannel) key.channel();
    sendText = "client[" + client.socket().getLocalAddress() + ":" + client.socket().getLocalPort() + "]:" + (flag++);
    sendBuffer.put(sendText.getBytes());
    //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
    sendBuffer.flip();
    client.write(sendBuffer);
    System.out.println("客户端向服务器端发送数据：" + sendText);
    client.register(selector, SelectionKey.OP_READ);
  }

  private void doRead(SelectionKey key) throws IOException {
    SocketChannel client;
    int count;
    String receiveText;
    client = (SocketChannel) key.channel();
    //将缓冲区清空以备下次读取
    receiveBuffer.clear();
    //读取服务器发送来的数据到缓冲区中
    count = client.read(receiveBuffer);
    if (count > 0) {
      receiveText = new String(receiveBuffer.array(), 0, count);
      System.out.println("客户端接受服务器端数据:" + receiveText);
      client.register(selector, SelectionKey.OP_WRITE);
    }
  }

  private void listen() throws IOException {
    // 分配缓冲区大小内存
    Set<SelectionKey> selectionKeys;
    Iterator<SelectionKey> iterator;
    SelectionKey key;
    while (true) {
      //选择一组键，其相应的通道已为 I/O 操作准备就绪。
      //此方法执行处于阻塞模式的选择操作。
      selector.select();
      //返回此选择器的已选择键集。
      selectionKeys = selector.selectedKeys();
      //System.out.println(selectionKeys.size());
      iterator = selectionKeys.iterator();
      while (iterator.hasNext()) {
        key = iterator.next();
        if (key.isConnectable()) {
          doConnect(key);
        } else if (key.isReadable()) {
          doRead(key);
        } else if (key.isWritable()) {
          doWrite(key);
        }
        try {
          TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException ignore) {
        }
      }
      selectionKeys.clear();
    }
  }

  public static void main(String[] args) throws IOException {
    new NIOClient().listen();
  }
}

